﻿using Raider.WinForms.Actions.Linkage;
using Raider.WinForms.Actions.Utils;
using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace Raider.WinForms.Actions
{
    /// <summary>
    /// Default implementation of the <see cref="IAction"/> interface.
    /// </summary>
    /// <remarks>
    /// List of the supported client controls and components:
    /// <list type="bullet">
    /// <item><see cref="Control"/></item>
    /// <item><see cref="ToolStripItem"/></item>
    /// <item><see cref="MenuItem"/></item>
    /// </list>
    /// </remarks>
    [DesignTimeVisible(false), ToolboxItem(false)]
    public abstract class BaseAction : Component, IAction
    {
        private readonly string defaultTitle;
        private string title;

        private string name;
        private bool enabled = true;
        private ActionUpdateKind updateKind;

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseAction"/>.
        /// </summary>
        /// <param name="title">Default title for the action (empty by default).</param>
        protected BaseAction(string title = "")
        {
            this.defaultTitle = title;
            this.title = title;
        }

        /// <summary>
        /// Gets or sets the name of the action.
        /// </summary>
        /// <value>Action's name, or <see cref="string.Empty"/> if name is not set.</value>
        /// <remarks>
        /// Like <see cref="Control.Name"/>, this property never returns <c>null</c>. If you set this property
        /// to <c>null</c>, it will return an empty string.
        /// </remarks>
        [Browsable(false)]
        public string Name
        {
            get { return DesignUtils.GetComponentName(this, name); }
            set
            {
                if (DesignMode)
                    return;

                if (name != value)
                {
                    name = value;
                    OnPropertyChanged("Name");
                }
            }
        }

        /// <summary>
        /// Gets or sets the text to be displayed on the controls linked with the action.
        /// </summary>
        /// <value>Action's title, or <see cref="string.Empty"/> if title is not set.</value>
        /// <remarks>
        /// Like <see cref="Control.Text"/>, this property never returns <c>null</c>. If you set this property
        /// to <c>null</c>, it will return an empty string.
        /// </remarks>
        [Localizable(true)]
        [Category("Appearance"), Description("Defines the text that appears on the client controls and menu items.")]
        [LinkWithProperty("Text", typeof(Control), typeof(ToolStripItem), typeof(MenuItem))]
        public string Title
        {
            get { return title; }
            set 
            {
                var newValue = value ?? "";
                if (title != newValue)
                {
                    title = newValue;
                    OnPropertyChanged("Title"); 
                }
            }
        }

        /// <summary>
        /// Gets or sets the value, indicating whether the action is enabled.
        /// </summary>
        [DefaultValue(true), Category("Behavior")]
        [Description("Indicates whether the action is enabled.\nNote: Some of the standard actions maintain this property automatically.")]
        [LinkWithProperty("Enabled", typeof(Control), typeof(ToolStripItem), typeof(MenuItem))]
        public bool Enabled
        {
            get { return enabled; }
            set 
            { 
                if (enabled != value) 
                { 
                    enabled = value; 
                    OnPropertyChanged("Enabled"); 
                } 
            }
        }

        /// <summary>
        /// Gets or sets a value that determines how the action is updated.
        /// </summary>
        [DefaultValue(ActionUpdateKind.Automatic), Category("Behavior")]
        [Description("Defines the way how the action is updated.")]
        public ActionUpdateKind UpdateKind 
        {
            get { return updateKind; }
            set
            {
                if (updateKind != value)
                {
                    updateKind = value;
                    OnPropertyChanged("UpdateKind");
                }
            }
        }

        /// <summary>
        /// Explicitly executes the action and returns the result.
        /// </summary>
        /// <param name="argument">Optional argument of any type, that will be passed 
        /// to <see cref="ExecuteEventArgs"/>.</param>
        /// <remarks>
        /// Note that action will be executed regardless of the <see cref="Enabled"/> property value.
        /// </remarks>
        /// <returns>
        /// User-defined result of the execution (<c>null</c> by default).
        /// </returns>
        public virtual object DoExecute(object argument = null)
        {
            var cancelEventArgs = new ExecutingEventArgs(false, argument);
            OnExecuting(cancelEventArgs);

            if (cancelEventArgs.Cancel)
            {
                return null;
            }

            var executeArgs = cancelEventArgs.GetExecuteArgs();
            OnExecute(executeArgs);

            var executedArgs = executeArgs.GetExecutedArgs();
            OnExecuted(executedArgs);

            return executedArgs.Result;
        }

        /// <summary>
        /// Explicitly updates the action.
        /// </summary>
        /// <param name="argument">Optional argument of any type, that will be passed
        /// to <see cref="UpdateEventArgs"/>.</param>
        /// <remarks>
        /// Note that action will be updated regardless of the <see cref="Enabled"/> property value.
        /// </remarks>
        public virtual void DoUpdate(object argument = null)
        {
            var cancelEventArgs = new CancelEventArgs();
            OnUpdating(cancelEventArgs);

            if (cancelEventArgs.Cancel)
            {
                return;
            }

            var updateEventArgs = argument == null ? UpdateEventArgs.Empty : new UpdateEventArgs(argument);
            OnUpdate(updateEventArgs);

            OnUpdated(EventArgs.Empty);
        }

        /// <summary>
        /// Handles the Click event of the targets linked with this Action.
        /// </summary>
        [LinkWithEvent("Click", typeof(Control), typeof(ToolStripItem), typeof(MenuItem))]
        protected virtual void OnTargetClicked(object sender, EventArgs args)
        {
            DoExecute(sender);
        }

        #region Events

        /// <summary>Occurs when action is going to be executed.</summary>
        [Category("Behavior"), Description("Occurs when action is going to be executed.")]
        public event ExecutingEventHandler Executing;

        /// <summary>
        /// Raises the <see cref="Executing" /> event.
        /// </summary>
        protected virtual void OnExecuting(ExecutingEventArgs args)
        {
            if (Executing != null)
            {
                Executing(this, args);
            }
        }

        /// <summary>Occurs when action is being executed.</summary>
        [Category("Behavior"), Description("Occurs when action is being executed.")]
        public event ExecuteEventHandler Execute;

        /// <summary>
        /// Raises the <see cref="Execute" /> event.
        /// </summary>
        protected virtual void OnExecute(ExecuteEventArgs args)
        {
            if (Execute != null)
            {
                Execute(this, args);
            }
        }

        /// <summary>Occurs when action has successfully executed.</summary>
        [Category("Behavior"), Description("Occurs when action has successfully executed.")]
        public event ExecutedEventHandler Executed;

        /// <summary>
        /// Raises the <see cref="Executed" /> event.
        /// </summary>
        protected virtual void OnExecuted(ExecutedEventArgs args)
        {
            if (Executed != null)
            {
                Executed(this, args);
            }
        }

        /// <summary>Occurs when action is going to be updated.</summary>
        [Category("Behavior"), Description("Occurs when action is going to be updated.")]
        public event CancelEventHandler Updating;

        /// <summary>
        /// Raises the <see cref="Updating" /> event.
        /// </summary>
        protected virtual void OnUpdating(CancelEventArgs args)
        {
            if (Updating != null)
            {
                Updating(this, args);
            }
        }

        /// <summary>Occurs when action is being updated.</summary>
        [Category("Behavior"), Description("Occurs when action is being updated.")]
        public event UpdateEventHandler Update;

        /// <summary>
        /// Raises the <see cref="Update" /> event.
        /// </summary>
        protected virtual void OnUpdate(UpdateEventArgs args)
        {
            if (Update != null)
            {
                Update(this, args);
            }
        }

        /// <summary>Occurs when action has successfully updated.</summary>
        [Category("Behavior"), Description("Occurs when action has successfully updated.")]
        public event EventHandler Updated;

        /// <summary>
        /// Raises the <see cref="Updated" /> event.
        /// </summary>
        protected virtual void OnUpdated(EventArgs args)
        {
            if (Updated != null)
            {
                Updated(this, args);
            }
        }

        #endregion

        #region INotifyPropertyChanged Implementation

        /// <summary>Occurs when a property value changes.</summary>
        [Browsable(false)]
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event.
        /// </summary>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        #region Required for VS designer

        /// <summary>
        /// Resets a value of <see cref="Title"/> property.
        /// </summary>
        public void ResetTitle()
        {
            Title = defaultTitle;
        }

        /// <summary/>
        protected bool ShouldSerializeTitle()
        {
            return Title != defaultTitle;
        }

        #endregion

        /// <summary>
        /// Gets a string representation of the action.
        /// </summary>
        public override string ToString()
        {
            var type = GetType();
            var typeName = type.Assembly == Constants.ActionsAssembly ? type.Name : type.FullName;
            var titleOrName = string.IsNullOrEmpty(Title) ? Name : Title;
            return titleOrName + " [" + typeName + "]";
        }
    }
}
